
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 *
 */

package com.hlkj.pay.service.token.impl;

import cn.hutool.extra.servlet.ServletUtil;
import com.hlkj.framework.caches.service.RedisTemplateService;
import com.hlkj.framework.common.provider.Ip2regionProvider;
import com.hlkj.framework.common.util.web.WebFrameworkUtils;
import com.hlkj.framework.extension.executor.ExtensionExecutor;
import com.hlkj.pay.common.constants.TokenRedisConstant;
import com.hlkj.pay.enums.AuthTokenEnum;
import com.hlkj.pay.infrastructure.model.token.TokenAuthLogDO;
import com.hlkj.pay.infrastructure.model.token.TokenSecretDO;
import com.hlkj.pay.service.token.ITokenService;
import com.hlkj.pay.util.JwtTokenUtils;
import com.hlkj.pay.util.TokenUtils;
import com.hlkj.pay.vo.common.resp.token.OauthTokenResp;
import com.hlkj.pay.vo.common.resp.token.TokenInfoResp;
import jodd.util.StringPool;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.ip2region.core.IpInfo;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

/**
 * @author HlpayTeam
 * @date 2024/09/27 17:51
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class TokenCommonService {

    // @Value("${auth.signingKey}")
    public final static String signingKey="123456";

    private final RedisTemplateService redisTemplateService;

    private final ExtensionExecutor extensionExecutor;

    private final ITokenService tokenService;
    private final Ip2regionProvider ip2regionProvider;

    /**
     * userTokenToken
     * 
     * @param tenantSecretDO
     * @param userId
     * @return
     */
    public OauthTokenResp mfaAccessToken(TokenSecretDO tenantSecretDO, Long userId) {
        String accessToken = token(tenantSecretDO.getAppId(),tenantSecretDO.getSysType(), null,tenantSecretDO.getMfaTokenValidity(), userId,
                AuthTokenEnum.AUTH_TOKEN_TYPE.MFA_ACCESS_TOKEN, Boolean.FALSE);
        TokenInfoResp tokenInfoResp = TokenUtils.parseToken(accessToken);
        OauthTokenResp oauthTokenResp = new OauthTokenResp();
        oauthTokenResp.setToken(accessToken);
        oauthTokenResp.setExpiresTime(tokenInfoResp.getExpireTime());
        return oauthTokenResp;
    }

    /**
     * userTokenToken
     * 
     * @param tenantSecretDO
     * @param userId
     * @return
     */
    public OauthTokenResp userToken(TokenSecretDO tenantSecretDO, Long userId,String sn) {
        String accessToken = token( tenantSecretDO.getAppId(), tenantSecretDO.getSysType(),sn,tenantSecretDO.getAccessTokenValidity(), userId,
                AuthTokenEnum.AUTH_TOKEN_TYPE.ACCESS_TOKEN, Boolean.FALSE);
        // String refreshToken = token( tenantSecretDO.getAppId(), tenantSecretDO.getAccessTokenValidity(), userId,
        //         AuthTokenEnum.AUTH_TOKEN_TYPE.REFRESH_TOKEN, Boolean.FALSE);
        TokenInfoResp tokenInfoResp = TokenUtils.parseToken(accessToken);
        OauthTokenResp oauthTokenResp = new OauthTokenResp();
        oauthTokenResp.setToken(accessToken);
        // oauthTokenResp.setRefreshToken(refreshToken);
        oauthTokenResp.setExpiresTime(tokenInfoResp.getExpireTime());
        return oauthTokenResp;
    }

    /**
     * clientToken
     * 
     * @param tenantSecretDO
     * @return
     */
    public OauthTokenResp clientToken(TokenSecretDO tenantSecretDO) {
        String accessToken = token(tenantSecretDO.getAppId(), tenantSecretDO.getSysType(),null,tenantSecretDO.getAccessTokenValidity(), null,
                AuthTokenEnum.AUTH_TOKEN_TYPE.ACCESS_TOKEN, Boolean.FALSE);
        String refreshToken = token(tenantSecretDO.getAppId(), tenantSecretDO.getSysType(),null,tenantSecretDO.getAccessTokenValidity(), null,
                AuthTokenEnum.AUTH_TOKEN_TYPE.REFRESH_TOKEN, Boolean.FALSE);
        TokenInfoResp tokenInfoResp = TokenUtils.parseToken(accessToken);
        OauthTokenResp oauthTokenResp = new OauthTokenResp();
        oauthTokenResp.setToken(accessToken);
        oauthTokenResp.setRefreshToken(refreshToken);
        oauthTokenResp.setExpiresTime(tokenInfoResp.getExpireTime());
        return oauthTokenResp;
    }

    /**
     * refreshToken
     * 
     * @param refreshTokenInfoResp
     * @param refreshToken
     * @param tenantSecretDO
     * @return
     */
    public OauthTokenResp refreshToken(TokenInfoResp refreshTokenInfoResp, String refreshToken, TokenSecretDO tenantSecretDO) {
        String accessToken = token(refreshTokenInfoResp.getAppId(), tenantSecretDO.getSysType(),refreshTokenInfoResp.getSn(),tenantSecretDO.getAccessTokenValidity(), refreshTokenInfoResp.getUserId(),
                AuthTokenEnum.AUTH_TOKEN_TYPE.ACCESS_TOKEN, Boolean.FALSE);
        TokenInfoResp tokenInfoResp = TokenUtils.parseToken(accessToken);
        OauthTokenResp oauthTokenResp = new OauthTokenResp();
        oauthTokenResp.setToken(accessToken);
        oauthTokenResp.setRefreshToken(refreshToken);
        oauthTokenResp.setExpiresTime(tokenInfoResp.getExpireTime());
        return oauthTokenResp;
    }

    /**
     *
     * @param tenantSecretDO
     * @param oauthTokenResp
     * @param authTokenClientType
     */
    public void addTenantAuthLog(TokenSecretDO tenantSecretDO, OauthTokenResp oauthTokenResp, AuthTokenEnum.AUTH_TOKEN_REQUEST_TYPE authTokenClientType) {
        try {
            HttpServletRequest request = WebFrameworkUtils.getRequest();
            TokenAuthLogDO tenantAuthLogDO = new TokenAuthLogDO();
            tenantAuthLogDO.setAppId(tenantSecretDO.getAppId());
            tenantAuthLogDO.setAuthorizedGrantTypes(authTokenClientType.getCode());
            switch (authTokenClientType) {
                case MFA:
                    tenantAuthLogDO.setMfaToken(oauthTokenResp.getToken());
                    break;
                default:
                    tenantAuthLogDO.setAccessToken(oauthTokenResp.getToken());
            }
            tenantAuthLogDO.setRefreshToken(oauthTokenResp.getRefreshToken());
            tenantAuthLogDO.setIp(ServletUtil.getClientIP(request, null));
            try {
                IpInfo ipInfo = ip2regionProvider.getIp2regionSearcher().memorySearch(tenantAuthLogDO.getIp());
                if (ipInfo != null) {
                    tenantAuthLogDO.setIpLocation(ipInfo.getAddress());
                }
            }catch (Exception e){
            
            }
            tokenService.addTenantAuthLog(tenantAuthLogDO);
        }
        catch (Exception e) {
            log.error("addTenantAuthLog error:{}", e.getMessage());
        }
    }

    /**
     * checkToken
     * 
     * @param token
     * @return
     */
    public boolean checkToken(String token) {
        return TokenUtils.checkToken(token, signingKey);
    }

    /**
     * 获取token
     * 
     * @param appId
     * @param tokenValidity
     * @param userId
     * @param authTokenType
     * @param cache
     * @return
     */
    String token(String appId, Integer sysType, String sn,Integer tokenValidity, Long userId, AuthTokenEnum.AUTH_TOKEN_TYPE authTokenType, Boolean cache) {
        String tokenKey = tokenKey(userId, authTokenType);
        if (cache) {
            Object accessTokenCache = redisTemplateService.get(tokenKey);
            if (accessTokenCache != null) {
                return accessTokenCache.toString();
            }
        }
        String token = JwtTokenUtils.generateToken(signingKey,  sysType,sn,userId, appId, tokenValidity, authTokenType);
        redisTemplateService.set(tokenKey, token, tokenValidity);
        return token;
    }

    /**
     * expireToken
     * 
     * @param tokenInfoResp
     */
    public void expireToken(TokenInfoResp tokenInfoResp) {
        Long userId = tokenInfoResp.getUserId();
        String accessTokenKey = tokenKey(userId, AuthTokenEnum.AUTH_TOKEN_TYPE.ACCESS_TOKEN);
        String refreshTokenKey = tokenKey( userId, AuthTokenEnum.AUTH_TOKEN_TYPE.REFRESH_TOKEN);
        List<String> key = new ArrayList<>();
        key.add(accessTokenKey);
        key.add(refreshTokenKey);
        redisTemplateService.del(key);
    }

    /**
     * tokenKey
     * 
     * @param userId
     * @param authTokenType
     * @return
     */
    String tokenKey(Long userId, AuthTokenEnum.AUTH_TOKEN_TYPE authTokenType) {
        if (userId == null) {
            return clientTokenKey(authTokenType);
        }
        else {
            return userTokenKey(userId, authTokenType);
        }
    }

    /**
     * userTokenKey
     * 
     * @param userId
     * @param authTokenRedisType
     * @return
     */
    String userTokenKey(Long userId, AuthTokenEnum.AUTH_TOKEN_TYPE authTokenRedisType) {
        StringBuilder sb = new StringBuilder(TokenRedisConstant.TOKEN_USER_AUTH);
        sb.append(userId);
        sb.append(StringPool.COLON);
        sb.append(authTokenRedisType.getName());
        return sb.toString();
    }

    /**
     * clientTokenKey
     * 
     * @param authTokenRedisType
     * @return
     */
    String clientTokenKey(AuthTokenEnum.AUTH_TOKEN_TYPE authTokenRedisType) {
        StringBuilder sb = new StringBuilder(TokenRedisConstant.TOKEN_CLIENT_AUTH);
        sb.append(authTokenRedisType.getName());
        return sb.toString();
    }

}
